package org.thrudb.thrudex.lucene;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.KeywordAnalyzer;
import org.apache.lucene.analysis.PerFieldAnalyzerWrapper;
import org.apache.lucene.analysis.SimpleAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.StopAnalyzer;
import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.apache.lucene.index.IndexReader;
import org.apache.thrift.TException;
import org.thrudb.thrudex.Document;
import org.thrudb.thrudex.Element;
import org.thrudb.thrudex.Field;
import org.thrudb.thrudex.SearchQuery;
import org.thrudb.thrudex.SearchResponse;
import org.thrudb.thrudex.ThrudexException;
import org.thrudb.thrudex.ThrudexExceptionImpl;
import org.thrudb.thrudex.Thrudex.Iface;
/**
* Manages a set of lucene indexes. We keep this one lucene backend per index
*
* @author jake
*
*/
public class ThrudexLuceneHandler implements Iface {
private volatile Map<Integer, Analyzer> analyzers = new HashMap<Integer, Analyzer>();
private Logger logger = Logger.getLogger(this.getClass().getSimpleName());
private volatile Map<String, LuceneIndex> indexMap = new HashMap<String, LuceneIndex>();
private String indexRoot;
public ThrudexLuceneHandler(String indexRoot) {
this.indexRoot = indexRoot;
Runtime.getRuntime()
.addShutdownHook(new IndexShutdownHandler(indexMap));
}
public String admin(String op, String data) throws ThrudexException,
TException {
if (op.equals("create_index"))
addIndex(data);
if (op.equals("optimize")) {
if (indexMap.containsKey(data)) {
try {
indexMap.get(data).optimize();
} catch (ThrudexException e) {
return e.toString();
}
}
}
return "ok";
}
public synchronized void addIndex(String name) throws ThrudexException {
if (name == null || name.trim().equals(""))
return;
if (indexMap.containsKey(name))
return;
try {
// indexMap.put(name, new SimpleLuceneIndex(indexRoot,name));
indexMap.put(name, new RealTimeLuceneIndex(indexRoot, name));
} catch (IOException e) {
throw new ThrudexException(e.toString());
}
}
/**
* Returns the list of available index names
*/
public List<String> getIndices() throws TException {
return new ArrayList<String>(indexMap.keySet());
}
/**
* This method does nothing, but lets client check the server
*/
public void ping() throws TException {
}
/**
* Add/Replace a document
*/
public void put(Document d) throws ThrudexException, TException {
// make sure index is valid
if (!isValidIndex(d.index))
throw new ThrudexExceptionImpl("No Index Found: " + d.index);
// make sure document has a key
if (!d.isSetKey() || d.key.trim().equals(""))
throw new ThrudexExceptionImpl("No Document key found");
// Start new lucene document
org.apache.lucene.document.Document luceneDocument = new org.apache.lucene.document.Document();
luceneDocument.add(new org.apache.lucene.document.Field(
LuceneIndex.DOCUMENT_KEY, d.key,
org.apache.lucene.document.Field.Store.YES,
org.apache.lucene.document.Field.Index.NOT_ANALYZED));
// Start analyzer
Analyzer defaultAnalyzer = getAnalyzer(org.thrudb.thrudex.Analyzer.STANDARD);
PerFieldAnalyzerWrapper qAnalyzer = new PerFieldAnalyzerWrapper(
defaultAnalyzer);
// Add fields
for (Field field : d.fields) {
if (!field.isSetKey())
throw new ThrudexExceptionImpl("Field key not set");
// Convert Field store type to Lucene type
org.apache.lucene.document.Field.Store fieldStoreType;
if (field.isStore())
fieldStoreType = org.apache.lucene.document.Field.Store.YES;
else
fieldStoreType = org.apache.lucene.document.Field.Store.NO;
// Create Lucene Field
org.apache.lucene.document.Field luceneField = new org.apache.lucene.document.Field(
field.key, field.value, fieldStoreType,
org.apache.lucene.document.Field.Index.ANALYZED);
if (field.isSetWeight())
luceneField.setBoost(field.weight);
luceneDocument.add(luceneField);
// Create sortable field?
if (field.isSetSortable() && field.sortable) {
luceneDocument.add(new org.apache.lucene.document.Field(
field.key + "_sort", field.value,
org.apache.lucene.document.Field.Store.YES,
org.apache.lucene.document.Field.Index.NOT_ANALYZED));
}
// Add field specific analyzer to qAnalyzer
qAnalyzer.addAnalyzer(field.key, getAnalyzer(field.getAnalyzer()));
}
// Add payload
if (d.isSetPayload()) {
luceneDocument.add(new org.apache.lucene.document.Field(
LuceneIndex.PAYLOAD_KEY, d.payload,
org.apache.lucene.document.Field.Store.YES,
org.apache.lucene.document.Field.Index.NOT_ANALYZED));
}
// Document is not ready to put into the index
indexMap.get(d.index).put(d.key, luceneDocument, qAnalyzer);
}
/**
* Adds a list of documents to an index
*
* Rather than returning on any error, this code captures any errors for
* specific documents and puts them into a list
*/
public List<ThrudexException> putList(List<Document> documents)
throws ThrudexException, TException {
List<ThrudexException> exList = new ArrayList<ThrudexException>();
for (Document document : documents) {
try {
put(document);
} catch (ThrudexException ex) {
ex.what += document.key;
exList.add(ex);
}
}
return exList;
}
/**
* Removes a document from an index
*/
public void remove(Element el) throws ThrudexException, TException {
// make sure index is valid
if (!isValidIndex(el.index))
throw new ThrudexExceptionImpl("No Index Found: " + el.index);
// make sure document has a key
if (!el.isSetKey() || el.key.trim().equals(""))
throw new ThrudexExceptionImpl("No Document key found");
indexMap.get(el.index).remove(el.key);
}
/**
* Removes a set of documents.
*
* Captures any errors for sub-documents
*/
public List<ThrudexException> removeList(List<Element> elements)
throws ThrudexException, TException {
List<ThrudexException> exList = new ArrayList<ThrudexException>();
for (Element el : elements) {
try {
remove(el);
} catch (ThrudexException ex) {
ex.what += el.key;
exList.add(ex);
}
}
return exList;
}
public SearchResponse search(SearchQuery s) throws ThrudexException,
TException {
// make sure index is valid
if (!isValidIndex(s.index))
throw new ThrudexExceptionImpl("No Index Found: " + s.index);
// Build the query analyzer
Analyzer defaultAnalyzer = getAnalyzer(s.getDefaultAnalyzer());
PerFieldAnalyzerWrapper qAnalyzer = new PerFieldAnalyzerWrapper(
defaultAnalyzer);
if (s.isSetFieldAnalyzers()) {
for (String field : s.fieldAnalyzers.keySet())
qAnalyzer.addAnalyzer(field, getAnalyzer(s.fieldAnalyzers
.get(field)));
}
return indexMap.get(s.index).search(s, qAnalyzer);
}
public List<SearchResponse> searchList(List<SearchQuery> queries)
throws ThrudexException, TException {
List<SearchResponse> responses = new ArrayList<SearchResponse>();
for (SearchQuery query : queries) {
responses.add(search(query));
}
return responses;
}
public boolean isValidIndex(String indexName) throws ThrudexException {
if (indexMap.containsKey(indexName))
return true;
synchronized (indexMap) {
// double lock check
if (indexMap.containsKey(indexName))
return true;
String indexLocation = indexRoot + "/" + indexName;
if (IndexReader.indexExists(indexLocation)) {
addIndex(indexName); // really just reopening
return true;
} else {
return false;
}
}
}
protected Analyzer getAnalyzer(int analyzerType) throws ThrudexException {
Analyzer analyzer = analyzers.get(analyzerType);
if (analyzer == null) {
synchronized (analyzers) {
//double lock check
if( (analyzer = analyzers.get(analyzerType)) != null)
return analyzer;
switch (analyzerType) {
case org.thrudb.thrudex.Analyzer.STANDARD:
analyzer = new StandardAnalyzer();
break;
case org.thrudb.thrudex.Analyzer.KEYWORD:
analyzer = new KeywordAnalyzer();
break;
case org.thrudb.thrudex.Analyzer.SIMPLE:
analyzer = new SimpleAnalyzer();
break;
case org.thrudb.thrudex.Analyzer.STOP:
analyzer = new StopAnalyzer();
break;
case org.thrudb.thrudex.Analyzer.WHITESPACE:
analyzer = new WhitespaceAnalyzer();
break;
default:
throw new ThrudexExceptionImpl("Unknown QueryAnalyzer: "
+ analyzerType);
}
analyzers.put(analyzerType, analyzer);
}
}
return (analyzer);
}
}